// Repurposed from my other project

//---------------------------------------------------------------------------------
// Copyright (c) 2008 Metaphor Game Engine Project
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
//     1. The origin of this software must not be misrepresented; you must not
//        claim that you wrote the original software. If you use this software
//        in a product, an acknowledgment in the product documentation would be
//        appreciated but is not required.
//
//     2. Altered source versions must be plainly marked as such, and must not be
//        misrepresented as being the original software.
//
//     3. This notice may not be removed or altered from any source
//        distribution.
//---------------------------------------------------------------------------------

#include <memory>
#include <vector>
#include <algorithm>

#include "VLPrerequisites.h"

#include "VLSingleton.h"
#include "enet/enet.h"

#ifndef __VLENetCpp_h__
#define __VLENetCpp_h__

class VLENetException : public std::exception
{
public:
  VLENetException(std::string m) : msg(m) {}
  virtual ~VLENetException() throw() {}

  const char* what() const throw() { return msg.c_str(); }

private:
  std::string msg;
};

//! Library that implements reliable and unreliable transfer via UDP
class VLENetHostWrapper;

//! Add a listener to know about connects, disconnects, and any packets received
struct VLIENetHostListener
{
  //! Is called when someone connects to the host
  virtual void onConnect(VLENetHostWrapper &host, ENetPeer *peer) { }

  //! Is called when someone disconnects from the host
  virtual void onDisconnect(VLENetHostWrapper &host, ENetPeer *peer, enet_uint32 reason) { }

  //! Is called when we receive a packet from someone
  virtual void onPacketReceived(VLENetHostWrapper &host, ENetPeer *peer, ENetPacket &packet, enet_uint8 channel) { }
};

//! Wraps an ENetHost to represent a server or client
class VLENetHostWrapper
{
public:
  //! Wraps an existing ENetHost
  VLENetHostWrapper(ENetHost *host, VLIENetHostListener *listener);
  virtual ~VLENetHostWrapper();

  //! Call this often to maintain responsiveness and let IENetHostListeners get called
  void process();
  
  //! Sends a packet to the specified peer.
  void sendPacket(ENetPeer *peer, ENetPacket *packet, enet_uint8 channel = 0);

  //! Sends a packet to every connected peer.

  /// A server can have any number of peers, so use this method to send to all
  /// peers at once
  void broadcastPacket(ENetPacket *packet, enet_uint8 channel = 0);

  //! Forcefully disconnect a specified peer.

  /// Disconnect a peer immediately. The reason can be used to tell a client
  /// why it was disconnected.
  void kick(ENetPeer *peer, unsigned int reason = 0);

  //! Gracefully disconnect a specified peer.

  /// Disconnect a peer very soon. The peer is guaranteed to know that it got 
  /// disconnected. Use the reason to tell the peer why it was disconnected
  void disconnect(ENetPeer *peer, unsigned int reason = 0);

  //! Gracefully disconnect everyone

  /// Calls disconnect() on every peer, use the reason to tell the peer why the
  /// disconnect occurred
  void disconnectAll(unsigned int reason = 0);
  

  //! Get the internal ENetHost
  ENetHost *getWrappedHost() { return mHost; }

private:
  ENetHost          *mHost;
  VLIENetHostListener *mListener;
};

//! Defines a smart pointer that ensures the host is cleaned up and not duplicated
typedef std::auto_ptr<VLENetHostWrapper> VLENetHostWrapperPtr;

//! Singleton that initializes the ENet library and creates ENetHosts

/// Create an ENetSystem anywhere before accessing as a singleton. Then, call createServer
/// or createClient to get a Host. All network operations should take place on a host or a 
/// peer that belongs to a host
class VLENetSystem : public VLSingleton<VLENetSystem>
{
public:

  //! Inits the ENet library and singleton instance
  VLENetSystem();
  ~VLENetSystem();

  //! Creates a server bound to a specific address
  VLENetHostWrapperPtr createServer(ENetAddress &addressToBind, 
                                    int clientsSupported, 
                                    VLIENetHostListener *listener);

  //! Creates a server bound to all IPs on the specified port
  VLENetHostWrapperPtr createServer(short port, 
                                    int clientsSupported,
                                    VLIENetHostListener *listener);

  //! Creates a client and tries to connect to a server on the specified port

  /// - server is a host name (i.e. localhost)
  /// - port is the remote port to connect to
  /// - the listener is called when we connect/disconnect/receive data
  /// - blockUntilConnected causes the function to block up to the
  /// - "timeout" value
  /// - if blockUntilConnected is false, the timeout is ignored
  VLENetHostWrapperPtr createClient(const std::string server, 
                                    short port,
                                    VLIENetHostListener *listener,
                                    bool blockUntilConnected = true,
                                    long timeout = 5000);

private:
  bool initSuccessful() { return (mENetInitReturnValue == 0); }
  int mENetInitReturnValue;
};

#endif
